package main

import (
	"archive/zip"
	"bytes"
	"crypto/x509"
	"errors"
	"howett.net/plist"
	"io"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

const APPLE_CA_CERT_URL = "https://www.apple.com/appleca/AppleIncRootCertificate.cer"
const LOCAL_CERT_FILENAME = "AppleIncRootCertificate.cer"

type Profile struct {
	AppIDName                   string     `plist:"AppIDName" json:"AppIDName"`
	ApplicationIdentifierPrefix []string   `plist:"ApplicationIdentifierPrefix" json:"ApplicationIdentifierPrefix"`
	CreationDate                *time.Time `plist:"CreationDate" json:"CreationDate"`
	Platform                    []string   `plist:"Platform" json:"Platform"`
	IsXcodeManaged              bool       `plist:"IsXcodeManaged" json:"IsXcodeManaged"`
	DeveloperCertificates       [][]byte   `plist:"DeveloperCertificates" json:"DeveloperCertificates"`
	DEREncodedProfile           []byte     `plist:"DER-Encoded-Profile" json:"DER-Encoded-Profile"`
	Entitlements                *InfoItems `plist:"Entitlements" json:"Entitlements"`
	ExpirationDate              *time.Time `plist:"ExpirationDate" json:"ExpirationDate"`
	Name                        string     `plist:"Name" json:"Name"`
	ProvisionsAllDevices        bool       `plist:"ProvisionsAllDevices" json:"ProvisionsAllDevices"`
	TeamIdentifier              []string   `plist:"TeamIdentifier" json:"TeamIdentifier"`
	TeamName                    string     `plist:"TeamName" json:"TeamName"`
	TimeToLive                  int16      `plist:"TimeToLive" json:"TimeToLive"`
	UUID                        string     `plist:"UUID" json:"UUID"`
	Version                     int16      `plist:"Version" json:"Version"`
}

func (this *Profile) GetDeveloperCerts() []*x509.Certificate {
	list := make([]*x509.Certificate, 0)
	for _, bin := range this.DeveloperCertificates {
		cert, err := x509.ParseCertificate(bin)
		if err != nil {
			continue
		}
		list = append(list, cert)
	}

	return list
}

type InfoItems struct {
	AppEnvironment        string   `plist:"aps-environment" json:"aps-environment"`
	ApplicatioIidentifier string   `plist:"application-identifier" json:"application-identifier"`
	KeychainAccessGroups  []string `plist:"keychain-access-groups" json:"keychain-access-groups"`
	GetTaskAllow          bool     `plist:"get-task-allow" json:"get-task-allow"`
	TeamIdentifier        string   `plist:"com.apple.developer.team-identifier" json:"com.apple.developer.team-identifier"`
}

func DownloadAppCACert(cacheDir string) (string, error) {
	if _, err := os.Stat(cacheDir); err != nil && os.IsNotExist(err) {
		os.MkdirAll(cacheDir, 0777)
	}
	certPath := filepath.Join(cacheDir, LOCAL_CERT_FILENAME)
	file, err := os.Create(certPath)
	if err != nil {
		log.Error(err)
		return "", err
	}
	defer file.Close()

	resp, err := http.Get(APPLE_CA_CERT_URL)
	if err != nil {
		log.Error(err)
		return "", err
	}
	defer resp.Body.Close()

	_, err = io.Copy(file, resp.Body)
	if err != nil {
		log.Error(err)
		return "", err
	}

	certPath, err = DERToPEM(certPath)
	if err != nil {
		log.Error(err)
		return "", err
	}

	return certPath, nil
}

func LoadAppleCACert(cacheDir string) (string, error) {
	certPath := filepath.Join(cacheDir, LOCAL_CERT_FILENAME)
	if _, err := os.Stat(certPath); err != nil && os.IsNotExist(err) {
		return DownloadAppCACert(cacheDir)
	}

	certPath, err := DERToPEM(certPath)
	if err != nil {
		return "", err
	}

	return certPath, nil
}

func DecryptProvision(provisionPath string, certPath string) (*bytes.Buffer, error) {
	cmd := exec.Command("openssl", "smime", "-in", provisionPath,
		"-inform", "der", "-verify", "-CAfile", certPath)
	output := new(bytes.Buffer)
	cmd.Stdout = output
	err := cmd.Run()
	if err != nil {
		log.Error(err)
		return nil, err
	}

	return output, nil
}

func DERToPEM(certPath string) (string, error) {
	pemPath := strings.Replace(certPath, ".cer", ".pem", 1)
	if _, err := os.Stat(pemPath); err == nil {
		return pemPath, nil
	}
	cmd := exec.Command("openssl", "x509", "-inform", "der", "-in", certPath, "-out", pemPath)
	err := cmd.Run()
	if err != nil {
		log.Error(err)
		return "", err
	}

	return pemPath, nil
}

func ParseProfile(xml *bytes.Buffer) (*Profile, error) {
	reader := bytes.NewReader(xml.Bytes())

	decoder := plist.NewDecoder(reader)

	data := new(Profile)
	err := decoder.Decode(data)
	if err != nil {
		return nil, err
	}

	return data, nil
}

func ExportProfileFromIPA(IPAPath string, profilePath string) error {
	reader, err := zip.OpenReader(IPAPath)
	if err != nil {
		return err
	}
	defer reader.Close()

	var profile *zip.File
	for _, info := range reader.File {
		if strings.Contains(info.Name, "mobileprovision") {
			profile = info
			break
		}
	}
	if profile == nil {
		return errors.New("profile not found")
	}

	file, err := profile.Open()
	if err != nil {
		return err
	}
	defer file.Close()

	proFile, err := os.Create(profilePath)
	if err != nil {
		return err
	}
	defer proFile.Close()

	_, err = io.Copy(proFile, file)
	if err != nil {
		return err
	}

	return nil
}
